#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
import asyncio
import functools
from datetime import datetime
from aiohttp import web
from sqlalchemy import func
from . import db


async def parse_param(request):
    params = {}
    if request.method == 'POST':
        if not request.content_type:
            raise web.HTTPBadRequest('Missing Content-Type.')
        ct = request.content_type.lower()
        if ct.startswith('application/json'):
            params = await request.json()
            if not isinstance(params, dict) and not isinstance(params, list):
                raise web.HTTPBadRequest('JSON body must be object.')
        else:
            post = await request.post()
            for k, v in post.items():
                params[k] = v
    if request.method == 'GET':
        for k, v in request.query.items():
            params[k] = v.strip().replace('%3B', ';').replace(
                '%3D', '=').replace('%2F', '/')
    return params


def printException(e=None):
    exc_type, exc_value, exc_traceback = sys.exc_info()
    exception = {
        'filename': exc_traceback.tb_frame.f_code.co_filename,
        'lineno': exc_traceback.tb_lineno,
        'message': exc_value
    }

    return "{filename}[{lineno}] - ERROR {message}".format(**exception)


def is_port_in_use(host, port):
    import socket
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        return s.connect_ex((host, port)) == 0


def template(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__template__ = text
        return wrapper
    return decorator


def bgTask():
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.task = True
        return wrapper
    return decorator


async def load_page(tableName, index, *, pagesize=12, filter={'name': None, 'status': None}, like={'name': None}, in_={}, order_by={'id': True, 'created_date': True}):
    table_ = db.dbs.get(tableName, None)
    if table_ is None:
        return {'page': {'total': 0, 'pages': 0, 'index': 1, 'size': pagesize}, 'items': []}
    session = db.create_session()
    filter_ = []
    if filter.get('path', None):
        filter_.append(table_.path == filter.get('path', '/'))
    for key, val in filter.items():
        if val is None:
            continue
        filter_.append(getattr(table_, key) == val)
    for key, val in like.items():
        if val is None:
            continue
        filter_.append(getattr(table_, key).like(f'%{val}%'))
    if len(in_.keys()) > 0:
        for key, val in in_.items():
            if not isinstance(val, list):
                continue
            filter_.append(getattr(table_, key).in_(val))
    order_by_ = []
    for key, rev in order_by.items():
        if rev:
            order_by_.append(table_.__dict__[key])
        else:
            order_by_.append(table_.__dict__[key].desc())
    total = session.query(func.count(table_.id)).filter(*filter_).scalar()
    items = [item.single_to_dict() for item in session.query(table_).filter(
        *filter_).order_by(*order_by_).offset((index - 1) * pagesize).limit(pagesize)]
    pages = ((total - 1) // pagesize) + 1 if pagesize > 0 else 0
    return {'page': {'total': total, 'pages': pages, 'index': index, 'size': pagesize}, 'items': items}


async def load_item(tableName, filter={'name': None, 'status': None}, like={}, in_={}, order_by={'id': True, 'created_date': True}):
    table_ = db.dbs.get(tableName, None)
    if table_ is None:
        return None
    session = db.create_session()
    filter_ = []
    if filter.get('path', None):
        filter_.append(table_.path == filter.get('path', '/'))
    for key, val in filter.items():
        if val is None:
            continue
        filter_.append(getattr(table_, key) == val)
    for key, val in like.items():
        if val is None:
            continue
        filter_.append(getattr(table_, key).like(f'%{val}%'))
    if len(in_.keys()) > 0:
        for key, val in in_.items():
            if not isinstance(val, list):
                continue
            filter_.append(getattr(table_, key).in_(val))
    order_by_ = []
    for key, rev in order_by.items():
        if rev:
            order_by_.append(table_.__dict__[key])
        else:
            order_by_.append(table_.__dict__[key].desc())
    item = session.query(table_).filter(*filter_).order_by(*order_by_).first()
    if item:
        return item.single_to_dict()


async def load_items(tableName, filter={'name': None, 'status': None}, like={}, in_={}, order_by={'id': True, 'created_date': True}):
    table_ = db.dbs.get(tableName, None)
    if table_ is None:
        return {'items': []}
    session = db.create_session()
    filter_ = []
    if filter.get('path', None):
        filter_.append(table_.path == filter.get('path', '/'))
    for key, val in filter.items():
        if val is None:
            continue
        filter_.append(getattr(table_, key) == val)
    for key, val in like.items():
        if val is None:
            continue
        filter_.append(getattr(table_, key).like(f'%{val}%'))
    if len(in_.keys()) > 0:
        for key, val in in_.items():
            if not isinstance(val, list):
                continue
            filter_.append(getattr(table_, key).in_(val))
    order_by_ = []
    for key, rev in order_by.items():
        if rev:
            order_by_.append(table_.__dict__[key])
        else:
            order_by_.append(table_.__dict__[key].desc())
    items = [item.single_to_dict() for item in session.query(
        table_).filter(*filter_).order_by(*order_by_)]
    return {'items': items}


def save(tableName='', items=[]):
    if len(tableName) is None:
        return
    table_ = db.dbs.get(tableName, None)
    if table_ is None:
        return
    if len(items) <= 0:
        return
    ids = []
    session = db.create_session()
    item_dict = {item['id']: item for item in items if item.__contains__('id')}
    # print('-' * 25)
    # print(items)
    # print('-' * 25)
    items_ = [item for item in session.query(
        table_).filter(table_.id.in_(item_dict.keys()))]

    for item in items_:
        for key, value in item_dict[item.id].items():
            if key.startswith('updated_') or key.startswith('created_'):
                continue
            setattr(item, key, value)
        item.updated_at = datetime.now()
        item.updated_date = item.updated_at.strftime('%Y%m%d')
        item.updated_stamp = item.updated_at.timestamp()

    for obj in items:
        if obj.__contains__('id'):
            continue
        item = table_()
        # print('-' * 25)
        # print(obj)
        # print('-' * 25)
        for key, value in obj.items():
            if key.startswith('updated_') or key.startswith('created_'):
                continue
            setattr(item, key, value)
        session.add(item)
        items_.append(item)
    session.commit()
    return items_


def delete(tableName='', ids=[], session=None):
    if len(tableName) is None:
        return
    table_ = db.dbs.get(tableName, None)
    if table_ is None:
        return
    if len(ids) <= 0:
        return
    if session is None:
        session = db.create_session()
    items = [item for item in session.query(table_).filter(table_.id.in_(ids))]
    for item in items:
        if item is not None and hasattr(item, 'content_type') and item.content_type == 'd':
            filter_ = []
            filter_.append(table_.path == f'{item.path}/{item.name}')
            delete(tableName, [item.id for item in session.query(table_).filter(*filter_)], session)
        session.delete(item)
    session.commit()


def execute_sql(sql):
    results = []
    try:
        session = db.create_session()
        sqls = sql.split(';')
        cud = []
        retrieve = []
        for item in sqls:
            item = item.strip()
            if len(item) <= 0:
                continue
            if item.upper().startswith('SELECT'):
                retrieve.append(item)
            else:
                cud.append(item)
        for item in sqls:
            item = item.strip()
            if len(item) <= 0:
                continue
            ret = session.execute('%s;' % item)
            if item in retrieve:
                ret = [dict((zip(item.keys(), item)))
                       for item in ret.fetchall()]
                results.append(ret)
            else:
                results.append({item: 'SUCCESS'})
        if len(cud) > 0:
            session.commit()
    except BaseException as e:
        return {'status': False, 'msg': e}
    return {'status': True, 'msg': results}


def async_run(runable=[]):
    loop = asyncio.get_event_loop()
    result = None
    try:
        if asyncio.iscoroutine(runable):
            pass
        if isinstance(runable, list):
            runable = asyncio.wait(runable)
        result = loop.run_until_complete(runable)
    except (KeyboardInterrupt):  # pragma: no cover
        pass
    finally:
        web._cancel_all_tasks(loop)
        if sys.version_info >= (3, 6):  # don't use PY_36 to pass mypy
            loop.run_until_complete(loop.shutdown_asyncgens())
        loop.close()
    return result
